﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Microsoft.Practices.Unity;

namespace EnterpriseLibraryExtensions.Events.Aggregation
{
    /// <summary>
    /// The FilteringScopeOptions class can be used to aggregate events intelligently by registering event comparers 
    /// on this class and then assign it to the Options property on the EventAggregationScope class.
    /// </summary>
    public sealed class FilteringScopeOptions : IScopeOptions
    {
        /// <summary>
        /// The comparer id is used when registering and retrieving comparers from the container. 
        /// This ensures that the comparers will not collide with other registrations on the same container
        /// with the same key type.
        /// </summary>
        internal const string ComparerId = "{51196B1F-5A6F-4F65-9CC3-01DD5A8687E3}";

        /// <summary>
        /// The container used for storing the comparers.
        /// </summary>
        private readonly IUnityContainer _unityContainer;

        /// <summary>
        /// The dictionary contains a list of all registered comparers.
        /// </summary>
        private readonly Dictionary<Type, Type> _registeredComparers;

        /// <summary>
        /// Initializes a new instance of the FilteringScopeOptions class. Internnaly, the class will use a new Unity container
        /// to hold the registered comparers.
        /// </summary>
        [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "The UnityContainer is copied to a field.")]
        public FilteringScopeOptions()
            : this(new UnityContainer())
        {
        }

        /// <summary>
        /// Initializes a new instance of the FilteringScopeOptions class. The provided Unity container will be used to hold
        /// registered comparers. If a container has been used in one filtering scope instance, it can be used in another without
        /// having to re-register the comparers as they are stored in the container.
        /// </summary>
        /// <param name="unityContainer">The valid Unity container to use for comparer storage.</param>
        /// <exception cref="System.ArgumentNullException">Thrown if the provided unityContainer instance is null.</exception>
        public FilteringScopeOptions(IUnityContainer unityContainer)
        {
            if (unityContainer == null)
            {
                throw new ArgumentNullException("unityContainer");
            }

            _unityContainer = unityContainer;
            _registeredComparers = new Dictionary<Type, Type>();

            EventAggregationTracer.TraceInformation("New FilteringScopeOptions instance created");
        }

        /// <summary>
        /// Returns a new list of EnlistedEventHandlers that has been filtered based on the registered comparers.
        /// </summary>
        /// <param name="enlistedEventHandlers">A list of EnlistedEventHandlers to filter.</param>
        /// <returns>A filtered list of EnlistedEventHandlers. The list might contain zero items.</returns>
        /// <exception cref="System.ArgumentNullException">Thrown if the provided enlistedEventHandlers list is null.</exception>
        public ReadOnlyCollection<IEnlistedEventHandler> RemoveDuplicates(IEnumerable<IEnlistedEventHandler> enlistedEventHandlers)
        {
            if (enlistedEventHandlers == null)
            {
                throw new ArgumentNullException("enlistedEventHandlers");
            }

            EventAggregationTracer.TraceInformation("Removing duplicates with registered comparers.");

            var filteredEventHandlers = new List<IEnlistedEventHandler>();

            foreach (var enlistedEventHandler in enlistedEventHandlers)
            {
                var comparer = GetComparer(enlistedEventHandler.EventHandler.EventType);

                bool addEnlistedEventHandler = true;

                if (comparer != null && filteredEventHandlers.Count > 0)
                {
                    IEnlistedEventHandler handler = enlistedEventHandler;

                    addEnlistedEventHandler = filteredEventHandlers.Count(feh => comparer.Compare(handler.Args, feh.Args) == 0) == 0;
                }

                if (addEnlistedEventHandler)
                {
                    filteredEventHandlers.Add(enlistedEventHandler);
                }
            }

            return new ReadOnlyCollection<IEnlistedEventHandler>(filteredEventHandlers);
        }

        /// <summary>
        /// Register a comparer instance for the designated event type. The comparer should not keep any state as it will be reused.
        /// </summary>
        /// <typeparam name="TEventType">The event type to register a comparer for.</typeparam>
        /// <param name="comparer">The comparer instance which cannot be null.</param>
        /// <exception cref="System.ArgumentNullException">Thrown if comparer is null.</exception>
        /// <exception cref="System.InvalidOperationException">Thrown if a comparer for the event type is already registered.</exception>
        public void RegisterComparer<TEventType>(IEventComparer<TEventType> comparer)
        {
            if (comparer == null)
            {
                throw new ArgumentNullException("comparer");
            }

            if (ComparerIsRegistered(typeof(TEventType)))
            {
                throw new InvalidOperationException("A comparer for the specified comparer type already exist.");
            }

            _unityContainer.RegisterInstance(ComparerId, comparer);
            _registeredComparers.Add(typeof(TEventType), typeof(IEventComparer<TEventType>));
        }

        /// <summary>
        /// Returns a comparer based on the provided eventType parameter.
        /// </summary>
        /// <param name="eventType">The eventType to get a comparer for.</param>
        /// <returns>The comparer registered for the provided eventType. If no comparer is registered for the type, the method returns null.</returns>
        internal IComparer GetComparer(Type eventType)
        {
            if (!ComparerIsRegistered(eventType))
            {
                EventAggregationTracer.TraceInformation("No comparer registered for event type {0}", eventType);

                return null;
            }

            var unityKey = _registeredComparers[eventType];

            EventAggregationTracer.TraceInformation("Returning comparer for event type {0}", eventType);

            return (IComparer)_unityContainer.Resolve(unityKey, ComparerId);
        }

        /// <summary>
        /// Method determines if a comparer for the provided eventType is registered.
        /// </summary>
        /// <param name="eventType">The eventType to examine if it has a comparer registered.</param>
        /// <returns>True if the eventType has a comparer registered.</returns>
        private bool ComparerIsRegistered(Type eventType)
        {
            return _registeredComparers.Keys.Any(k => k == eventType);
        }
    }
}
